﻿using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using FreshMeat.Storage;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Windows.Storage;

namespace Storage.More.Test.win8
{
   [TestClass]
   public class StorageFileSystemTest
   {
      [TestMethod]
      public async Task CreatesFolderAsync()
      {
         var system = await CreateSystem();

         await system.CreateFolderAsync("folder1");

         var exists = await system.FolderExistsAsync("folder1");
         Assert.IsTrue(exists, "Folder should exist after being created.");
      }

      [TestMethod]
      public async Task CreatesFolderWithEmptyPathAsync()
      {
         var system = await CreateSystem();

         await system.CreateFolderAsync("");

         var exists = await system.FolderExistsAsync("");
         Assert.IsTrue(exists, "Folder should exist after being created.");
      }

      [TestMethod]
      public async Task CreatesNestedFoldersAsync()
      {
         var system = await CreateSystem();

         await system.CreateFolderAsync("folder1\\folder2\\folder3");

         var exists1 = await system.FolderExistsAsync("folder1");
         Assert.IsTrue(exists1, "folder1 should exist after being created.");
         var exists2 = await system.FolderExistsAsync("folder1\\folder2");
         Assert.IsTrue(exists2, "folder2 should exist after being created.");
         var exists3 = await system.FolderExistsAsync("folder1\\folder2\\folder3");
         Assert.IsTrue(exists3, "folder3 should exist after being created.");
      }

      [TestMethod]
      public async Task FolderExistsReturnsTrueAsync()
      {
         var system = await CreateSystem();
         await system.CreateFolderAsync("folder1");

         var exists = await system.FolderExistsAsync("folder1");

         Assert.IsTrue(exists, "Folder does exist.");
      }

      [TestMethod]
      public async Task FolderExistsReturnsFalseAsync()
      {
         var system = await CreateSystem();

         var exists = await system.FolderExistsAsync("NonExistingFolder");

         Assert.IsFalse(exists, "Folder does not exist.");
      }

      [TestMethod]
      public async Task DeleteExistingFolderAsync()
      {
         var system = await CreateSystem();
         await system.CreateFolderAsync("folder1");

         var existed = await system.DeleteFolderAsync("folder1");

         Assert.IsTrue(existed, "Folder existed before being deleted.");
      }

      [TestMethod]
      public async Task DeleteNonExistingFolderAsync()
      {
         var system = await CreateSystem();

         var existed = await system.DeleteFolderAsync("folder1");

         Assert.IsFalse(existed, "Folder did not exist.");
      }

      [TestMethod]
      public async Task DeletedFolderNoLongExistsAsync()
      {
         var system = await CreateSystem();
         await system.CreateFolderAsync("folder1");
         await system.DeleteFolderAsync("folder1");

         var exists = await system.FolderExistsAsync("folder1");

         Assert.IsFalse(exists, "Folder should no longer exist after being deleted.");
      }

      [TestMethod]
      public async Task FileExistsReturnsTrueAsync()
      {
         var system = await CreateSystem();
         await system.WriteAllTextAsync("file1.txt", "abc");

         var exists = await system.FileExistsAsync("file1.txt");

         Assert.IsTrue(exists, "File does exist.");
      }

      [TestMethod]
      public async Task FileExistsReturnsFalseAsync()
      {
         var system = await CreateSystem();

         var exists = await system.FileExistsAsync("NonExistingFile.txt");

         Assert.IsFalse(exists, "File does not exist.");
      }

      [TestMethod]
      public async Task DeleteExistingFileAsync()
      {
         var system = await CreateSystem();
         await system.WriteAllTextAsync("file1.txt", "abc");

         var existed = await system.DeleteFileAsync("file1.txt");

         Assert.IsTrue(existed, "File existed before being deleted.");
      }

      [TestMethod]
      public async Task DeleteNonExistingFileAsync()
      {
         var system = await CreateSystem();

         var existed = await system.DeleteFileAsync("file1.txt");

         Assert.IsFalse(existed, "File did not exist.");
      }

      [TestMethod]
      public async Task DeletedFileNoLongExistsAsync()
      {
         var system = await CreateSystem();
         await system.WriteAllTextAsync("file1.txt", "abc");
         await system.DeleteFileAsync("file1.txt");

         var exists = await system.FileExistsAsync("file1.txt");

         Assert.IsFalse(exists, "File should no longer exist after being deleted.");
      }

      [TestMethod]
      public async Task EnumerateFilesNonRecursiveAsync()
      {
         var system = await CreateSystem();
         await system.WriteAllTextAsync("file1.txt", "abc");
         await system.WriteAllTextAsync("file2.txt", "abc");
         await system.CreateFolderAsync("sub1");
         await system.WriteAllTextAsync("sub1\\file1.txt", "abc");
         await system.WriteAllTextAsync("sub1\\file2.txt", "abc");
         await system.CreateFolderAsync("sub2");
         await system.WriteAllTextAsync("sub2\\file1.txt", "abc");
         await system.WriteAllTextAsync("sub2\\file2.txt", "abc");
         await system.CreateFolderAsync("sub1\\sub2");
         await system.WriteAllTextAsync("sub1\\sub2\\file1.txt", "abc");
         await system.WriteAllTextAsync("sub1\\sub2\\file2.txt", "abc");

         var files = (await system.EnumerateFilesAsync("", "*.txt", false)).OrderBy(path => path).ToArray();

         Assert.AreEqual(2, files.Length, "Wrong number of files found.");
         Assert.AreEqual("file1.txt", files[0]);
         Assert.AreEqual("file2.txt", files[1]);
      }

      [TestMethod]
      public async Task EnumerateFilesRecursivelyAsync()
      {
         var system = await CreateSystem();
         await system.WriteAllTextAsync("file1.txt", "abc");
         await system.WriteAllTextAsync("file2.txt", "abc");
         await system.CreateFolderAsync("sub1");
         await system.WriteAllTextAsync("sub1\\file1.txt", "abc");
         await system.WriteAllTextAsync("sub1\\file2.txt", "abc");
         await system.CreateFolderAsync("sub1\\sub2");
         await system.WriteAllTextAsync("sub1\\sub2\\file1.txt", "abc");
         await system.WriteAllTextAsync("sub1\\sub2\\file2.txt", "abc");
         await system.CreateFolderAsync("sub2");
         await system.WriteAllTextAsync("sub2\\file1.txt", "abc");
         await system.WriteAllTextAsync("sub2\\file2.txt", "abc");

         var files = (await system.EnumerateFilesAsync("", "*.txt", true)).OrderBy(path => path).ToArray();

         Assert.AreEqual(8, files.Length, "Wrong number of files found.");
         Assert.AreEqual("file1.txt", files[0]);
         Assert.AreEqual("file2.txt", files[1]);
         Assert.AreEqual("sub1\\file1.txt", files[2]);
         Assert.AreEqual("sub1\\file2.txt", files[3]);
         Assert.AreEqual("sub1\\sub2\\file1.txt", files[4]);
         Assert.AreEqual("sub1\\sub2\\file2.txt", files[5]);
         Assert.AreEqual("sub2\\file1.txt", files[6]);
         Assert.AreEqual("sub2\\file2.txt", files[7]);
      }

      [TestMethod]
      public async Task MoveFileWhenDestinationExistsThrowsAsync()
      {
         var system = await CreateSystem();
         await system.WriteAllTextAsync("file1.txt", "abc");
         await system.CreateFolderAsync("folder1");
         await system.WriteAllTextAsync("folder1\\file1.txt", "abc");

         await AssertHelper.ThrowsException<InvalidOperationException>(
            () => system.MoveFileAsync("file1.txt", "folder1\\file1.txt"),
            false,
            "Should have thrown because destination already existed.");
      }

      [TestMethod]
      public async Task MoveFileWhenSourceDoesntExistThrowsAsync()
      {
         var system = await CreateSystem();
         await system.CreateFolderAsync("folder1");
         await system.WriteAllTextAsync("folder1\\file1.txt", "abc");

         await AssertHelper.ThrowsException<InvalidOperationException>(
            () => system.MoveFileAsync("file1.txt", "folder1\\file1.txt"),
            false,
            "Should have thrown because source file doesn't exist.");
      }

      [TestMethod]
      public async Task MoveWithoutRenameWorksAsync()
      {
         var system = await CreateSystem();
         await system.WriteAllTextAsync("file1.txt", "abc");
         await system.CreateFolderAsync("folder1");

         await system.MoveFileAsync("file1.txt", "folder1\\file1.txt");

         Assert.IsFalse(await system.FileExistsAsync("file1.txt"), "Source should no longer exist.");
         Assert.IsTrue(await system.FileExistsAsync("folder1\\file1.txt"), "Destination should exist.");
      }

      [TestMethod]
      public async Task MoveWithRenameWorksAsync()
      {
         var system = await CreateSystem();
         await system.WriteAllTextAsync("file1.txt", "abc");
         await system.CreateFolderAsync("folder1");

         await system.MoveFileAsync("file1.txt", "folder1\\file2.txt");

         Assert.IsFalse(await system.FileExistsAsync("file1.txt"), "Source should no longer exist.");
         Assert.IsTrue(await system.FileExistsAsync("folder1\\file2.txt"), "Destination with new name should exist.");
      }

      [TestMethod]
      public async Task MoveFileToItselfDoesNothingAsync()
      {
         var system = await CreateSystem();
         await system.WriteAllTextAsync("file1.txt", "abc");
         await system.CreateFolderAsync("folder1");

         await system.MoveFileAsync("file1.txt", "file1.txt");

         var exists = await system.FileExistsAsync("file1.txt");
         Assert.IsTrue(exists, "Source should still exist.");
      }

      [TestMethod]
      public async Task WritesAllTextCreatesFileAsync()
      {
         var system = await CreateSystem();

         await system.WriteAllTextAsync("file1.txt", "abc");

         var exists = await system.FileExistsAsync("file1.txt");
         Assert.IsTrue(exists, "File should exist after being created.");
      }

      [TestMethod]
      public async Task WritesAndReadsAllTextAsync()
      {
         var system = await CreateSystem();
         await system.WriteAllTextAsync("file1.txt", "abc");

         var contents = await system.ReadAllTextAsync("file1.txt");

         Assert.AreEqual("abc", contents, "Wrong data was either written or read.");
      }

      [TestMethod]
      public async Task WritesAllTextOverwritesFileAsync()
      {
         var system = await CreateSystem();
         await system.WriteAllTextAsync("file1.txt", "abc");

         await system.WriteAllTextAsync("file1.txt", "xyz"); // should overwrite existing file

         var contents = await system.ReadAllTextAsync("file1.txt");
         Assert.AreEqual("xyz", contents, "Wrong data was either written or read.");
      }

      [TestMethod]
      public async Task OpenForWriteCreatesFileAsync()
      {
         var system = await CreateSystem();

         using (var writeStream = await system.OpenForWriteAsync("file1.txt"))
         using (var writer = new StreamWriter(writeStream))
            writer.Write("abc");

         var exists = await system.FileExistsAsync("file1.txt");
         Assert.IsTrue(exists, "File should exist after writing to it.");
      }

      [TestMethod]
      public async Task OpenForWriteOverwritesFileAsync()
      {
         var system = await CreateSystem();
         await system.WriteAllTextAsync("file1.txt", "already exists");

         using (var writeStream = await system.OpenForWriteAsync("file1.txt"))
         using (var writer = new StreamWriter(writeStream))
            writer.Write("abc");

         var contents = await system.ReadAllTextAsync("file1.txt");
         Assert.AreEqual("abceady exists", contents, "File contents should have been partially overwritten by new text.");
      }

      [TestMethod]
      public async Task OpenForWritePreventsAnotherReaderAsync()
      {
         var system = await CreateSystem();
         using (var writeStream = await system.OpenForWriteAsync("file1.txt"))
         using (new StreamWriter(writeStream))
         {

            // Exception is least-common-denominator between platforms for this situation
            await AssertHelper.ThrowsException<Exception>(() => system.ReadAllTextAsync("file1.txt"), true);
         }
      }

      [TestMethod]
      public async Task OpenForWritePreventsAnotherWriterAsync()
      {
         var system = await CreateSystem();
         using (var writeStream = await system.OpenForWriteAsync("file1.txt"))
         using (new StreamWriter(writeStream))
         {

            // Exception is least-common-denominator between platforms for this situation
            await AssertHelper.ThrowsException<Exception>(() => system.WriteAllTextAsync("file1.txt", "lkajsdlkj"), true);
         }
      }

      async Task<IFileSystem> CreateSystem()
      {
         var folder = await ApplicationData.Current.TemporaryFolder.CreateFolderAsync(TestContext.TestName, CreationCollisionOption.GenerateUniqueName);

         return new StorageFileSystem(folder);
      }

      // ReSharper disable MemberCanBePrivate.Global
      // ReSharper disable UnusedAutoPropertyAccessor.Global
      public TestContext TestContext { get; set; }
      // ReSharper restore UnusedAutoPropertyAccessor.Global
      // ReSharper restore MemberCanBePrivate.Global
   }
}